#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "ClassTree.h"

#include "Class.h"
#include "Zoomer.h"
#include "OrdColl.h"
#include "Set.h"
#include "AccessMem.h"
#include "ClassManager.h"
#include "String.h"
#include "System.h"
#include "Menu.h"
#include "MenuBar.h"
#include "Box.h"

#include "ClassItem.h"
#include "ProgEnv.h"
#include "EtPeCmdNo.h"
#include "EtPeManager.h"

static RGBColor *c1;
static OrdCollection *treeViews= 0;

ONEXIT(PeClassTree)
{
	SafeDelete(c1);
}

//---- PeClassTreeView ----------------------------------------------------------

NewMetaImpl0(PeClassTreeView, TreeView);

PeClassTreeView::PeClassTreeView(EvtHandler *eh, Class *root)
									: TreeView(eh, eTLLeftRight, eTCDiagonal2)
{
	if (treeViews == 0)
		treeViews= new OrdCollection;
	treeViews->Add(this);

	gClassManager->SetupSubclasses();
	classes= new OrdCollection(200);
	InstallTree(root);
	show= ePeCTShowAll;
	references= new OrdCollection;
}

PeClassTreeView::~PeClassTreeView()
{
	SafeDelete(classes);
	treeViews->RemovePtr(this);
	if (treeViews->Size() == 0)
		SafeDelete(treeViews);
	if (references) {
		references->FreeAll();
		SafeDelete(references);
	}
}

VObject *PeClassTreeView::NodeAsVObject(Object *op)
{
	Class *cl= Guard(op, Class);
	VObject *vop= new PeClassItem(cIdClassItem, cl);
	classes->Add(vop);
	return vop;
}

Iterator *PeClassTreeView::MakeChildrenIter(Object *op)
{
	return Guard(op, Class)->SubclassIterator();
}

Command *PeClassTreeView::GetNodeSelector(TreeNode *tn, int cl)
{
	VObject *vop= tn->At(0)->FindItem(cIdClassItem);
	if (vop && vop->IsKindOf(PeClassItem) && vop->TestFlag(eClItemCollapsed))
		return new PeNameTreeNodeSelector(tn, cl);
	return TreeView::GetNodeSelector(tn, cl);
}

void PeClassTreeView::ShowClass(Class *cl)
{
	PeClassItem *ci= FindClassItem(cl);
	if (ci) {
		RevealRect(ci->ContentRect().Expand(100), Point(0, 100));
		SetSelection((VObject*)ci->GetNextHandler());
		UpdateEvent();
	}
}

void PeClassTreeView::SetSelection(VObject *vop)
{
	TreeView::SetSelection(vop);
	SynchSelection();
}

void PeClassTreeView::SynchSelection()
{
	VObject *sel= GetSelection();
	PeClassItem *cl= 0, *cl2= 0;

	if (sel)
		cl= Guard(sel->FindItem(cIdClassItem), PeClassItem);
	if (treeViews && cl) {
		Iter next(treeViews);
		PeClassTreeView *tvp;
		while (tvp= (PeClassTreeView*)next()) {
			VObject *sel2= tvp->GetSelection();
			if (sel2)
				cl2= Guard(sel2->FindItem(cIdClassItem), PeClassItem);
			if (cl2->GetClass() != cl->GetClass())
				tvp->ShowClass(cl->GetClass());
		}
	}
}

Class *PeClassTreeView::GetRootClass()
{
	TreeNode *tn= GetTree();
	PeClassItem *cl= Guard(GetTree()->FindItem(cIdClassItem), PeClassItem);
	return cl->GetClass();
}

void PeClassTreeView::ShowAllClasses()
{
	show= ePeCTShowAll;
	classes->ForEach(PeClassItem,ResetFlag)(eClItemCollapsed);
	CalcLayout();
}

void PeClassTreeView::ShowOnlyAbstractClasses()
{
	Iter next(classes);
	PeClassItem *cl;
	show= ePeCTShowAbstract;
	classes->ForEach(PeClassItem,ResetFlag)(eClItemCollapsed);
	while (cl= (PeClassItem*)next())
		if (!cl->GetClass()->IsAbstract())
			cl->SetFlag(eClItemCollapsed);
	CalcLayout();
}

void PeClassTreeView::ShowOnlyApplicationClasses()
{
	static char *etsrc= 0;
	Iter next(classes);
	PeClassItem *cl;
	char buf[1024];
	if (etsrc == 0)
		etsrc= strprintf("%s/src", gEtDir);
	int len= strlen(etsrc);

	show= ePeCTShowApp;
	classes->ForEach(PeClassItem,ResetFlag)(eClItemCollapsed);
	while (cl= (PeClassItem*)next()) {
		Class *cp= cl->GetClass();
		if (gEtPeManager->FileOfClass(cp, buf, TRUE)) {
			if (strncmp(etsrc, buf, len) == 0)
				cl->SetFlag(eClItemCollapsed);
		}
	}
	CalcLayout();
}

PeCTreeShow PeClassTreeView::Shows()
{
	return show;
}

void PeClassTreeView::DoSetupMenu(Menu *menu)
{
	TreeView::DoSetupMenu(menu);
	VObject *sel= GetSelection();
	if (sel) {
		TreeNode *tn= FindTreeNode(sel);
		char *current;
		if (tn->Collapsed())
			current= "Expand";
		else
			current= "Collapse";
		menu->ReplaceItem(cDOCOLLAPSE, current);
	}
}

Command *PeClassTreeView::DoMenuCommand(int cmd)
{
	VObject *sel= GetSelection();
	PeClassItem *cl= 0;

	if (sel)
		cl= Guard(selection->FindItem(cIdClassItem), PeClassItem);

	switch (cmd) {
	case cMEMBERS:
		ShowMembers(cl);
		break;
		
	case cCLIENTS:
		ShowAllClients(cl);
		break;
		
	case cSOURCE:
		Control(cPeHierBrowser, cPeShowBrowser, cl->GetClass());
		break;
		
	case cSPAWN:
		Control(cPeHierBrowser, cPeSpawnHier, gClassManager->Find("Object"));
		break;
		
	case cDOCOLLAPSE:
		Collapse(FindTreeNode(selection));
		break;
		
	case cPROMOTE:
		Control(cPeHierBrowser, cPeSpawnHier, cl->GetClass());
		break;
		
	case cTREEVIEW:
		SetLayout(eTLLeftRight);
		break;

	case cOUTLINEVIEW:
		SetLayout(eTLIndented);
		break;

	default:
		return TreeView::DoMenuCommand(cmd);
	}
	return gNoChanges;
}

void PeClassTreeView::Draw(Rectangle r)
{
	if (references) {
		references->ForEach(PeClassReference,Draw)(r);
	}
	TreeView::Draw(r);
}

void PeClassTreeView::AddReference(PeClassReference *cr)
{
	references->Add(cr);
	InvalidateRect(cr->BBox());
}

void PeClassTreeView::RemoveAllReferences()
{
	references->FreeAll();
	references->Empty(0);
	ForceRedraw();
}

void PeClassTreeView::OpenTreeNode(TreeNode *tn, bool)
{
	if (references->Size() == 0)
		return;
	Iter next(references);
	VObject *ci= tn->At(0)->FindItem(cIdClassItem);
	if (ci && ci->IsKindOf(PeClassItem)) {
		PeClassReference *cr;
		while (cr= (PeClassReference*)next()) {
			if (cr->Includes((PeClassItem*)ci))
				InvalidateRect(cr->BBox());
		}
	}
}

//---- PeCollectClassMembers -----------------------------------------------------

class PeCollectClassMembers : public AccessMembers {
	Collection *cp;
public:
	PeCollectClassMembers()
		{ cp= new OrdCollection; }
	~PeCollectClassMembers()
		{ SafeDelete(cp); }
	void Member(char *, int, int, int, Class *cl, bool, bool)
		{ cp->Add(cl); }
	Collection *GetCollection()
		{ return cp; }
};

void PeClassTreeView::ShowMembers(PeClassItem *ci)
{
	PeCollectClassMembers clm;
	Class *cp= ci->GetClass(), *clp;
	Collection *colItems= new OrdCollection;

	cp->EnumerateMyMembers(&clm);
	Iter next(clm.GetCollection());
	while (clp= (Class *)next())
		colItems->Add(FindClassItem(clp));
	AddReference (new PeMemberReference(ci, colItems));
}

//---- PeCollectClients ----------------------------------------------------------

class PeCollectClients : public AccessMembers {
	Collection *colp;
	Class *clp, *current;
public:
	PeCollectClients(Class *c)
		{ colp= new Set; clp= c; }
	~PeCollectClients()
		{ SafeDelete(colp); }
	void SetCurrent(Class *cp)
		{ current= cp; }
	void Member(char *, int, int, int, Class *cl, bool, bool)
		{ if (clp == cl) colp->Add(current); }
	Collection *GetCollection()
		{ return colp; }
};

void PeClassTreeView::ShowAllClients(PeClassItem *ci)
{
	Class *cp;
	PeCollectClients clm(ci->GetClass());
	Iter next(gClassManager->Iterator());
	while (cp= (Class*)next()) {
		clm.SetCurrent(cp);
		cp->EnumerateMyMembers(&clm);
	}
	Collection *colItems= new OrdCollection;
	Iter next2(clm.GetCollection());
	while (cp= (Class *)next2())
		colItems->Add(FindClassItem(cp));
	AddReference (new PeClassReference(ci, colItems, ePatBlack));
}

PeClassItem *PeClassTreeView::FindClassItem(Class *cp)
{
	PeClassItem dummy(0,cp);
	return (PeClassItem*)classes->Find(&dummy);
}

TreeNode *PeClassTreeView::FindTreeNode(VObject *vop)
{
	return (TreeNode*)vop->FindNextHandlerOfClass(Meta(TreeNode));
}

//---- NameTreeNodeSelector ----------------------------------------------------

PeNameTreeNodeSelector::PeNameTreeNodeSelector(TreeNode *tn, int cl)
{
	item= tn;
	lastinside= FALSE;
	clicks= cl;
	fp= gFixedFont->WithFace(eFaceBold);
	VObject *vop= tn->At(0)->FindItem(cIdClassItem);
	if (vop && vop->IsKindOf(PeClassItem))
		name= ((PeClassItem*)vop)->ClassName();
	p= vop->ContentRect().NE();
}

void PeNameTreeNodeSelector::TrackFeedback(Point, Point, bool)
{
	if (item && (lastinside != inside)) {
		GrShowString(fp, ePatXor, p, (byte*) name);
		item->At(0)->Outline(0);
		lastinside= inside;
	}
}

Command *PeNameTreeNodeSelector::TrackMouse(TrackPhase atp, Point, Point, Point np)
{
	VObject *image= item->At(0);
	inside= image->ContainsPoint(np);
	if (atp == eTrackRelease) {
		if (item && lastinside) {
			GrShowString(fp, ePatXor, p, (byte*) name);
			image->Outline(0);
			item->GetTreeView()->SetSelection(image);
			return item->GetTreeView()->NodeSelected(item->At(0), clicks);
		}
	}
	return this;
}

//---- PeClassReference --------------------------------------------------------------------------

PeClassReference::PeClassReference(PeClassItem *c, Collection *refs, Ink *p, int w)
{
	cl= c;
	nodes= refs;
	width= w;
	pat= p;
}

PeClassReference::~PeClassReference()
{
	SafeDelete(nodes);
}

bool PeClassReference::Includes(PeClassItem *ci)
{
	return nodes->ContainsPtr(ci) || (ci == cl);
}

void PeClassReference::Draw(Rectangle)
{
	PeClassItem *clp;
	Iter next(nodes);

	if (!cl->IsOpen())
		return;
	GrSetPenNormal();
	GrStrokeRect(cl->contentRect.Expand(1));
	while (clp= (PeClassItem*)next()) {
		if (clp->IsOpen()) {
			GrSetPenNormal();
			GrStrokeRect(clp->ContentRect().Expand(1));
			DrawConnection(cl, clp);
		}
	}
}

void PeClassReference::DrawConnection(PeClassItem *from, PeClassItem *to)
{
	GrMoveto(from->contentRect.Center());
	GrSetPenSize(1);
	SetWidthAndPattern(from, to);
		
	if (from == to)
		CircularReference(from);
	else {
		GrMoveto(from->contentRect.Center());
		GrLineto(to->contentRect.Center());
	}
}

static RGBColor *c2;

void PeClassReference::SetWidthAndPattern(PeClassItem*, PeClassItem*)
{
	if (c2 == 0)
		c2= new_RGBColor(0, 256, 0);
	GrSetPenSize(width);
	GrSetPenInk(c2);
}

void PeClassReference::CircularReference(PeClassItem *ci)
{
	int d= ci->ContentRect().Height();
	Rectangle rr(ci->ContentRect().NE()-Point(d/2), Point(d));
	GrStrokeOval(rr);
}

Rectangle PeClassReference::BBox()
{
	Rectangle r;
	VObject *clp;
	Iter next(nodes);

	r= cl->ContentRect().Expand(20);
	while ((clp= (VObject *)next()))
		r.Merge(clp->ContentRect().Expand(4));
	return r;
}

//---- PeMemberReference ---------------------------------------------------------

PeMemberReference::PeMemberReference(PeClassItem *cl, Collection *n) :
													PeClassReference(cl, n)
{
}

void PeMemberReference::SetWidthAndPattern(PeClassItem*, PeClassItem*)
{
	if (c1 == 0)
		c1= new RGBColor(256, 0, 0);
	GrSetPenInk(c1);
}

//---- PeHierarchyBrowser --------------------------------------------------------

NewMetaImpl(PeHierarchyBrowser,EtPeTool, (TP(ct)));

PeHierarchyBrowser *PeHierarchyBrowser::hierBrowser;

PeHierarchyBrowser::PeHierarchyBrowser(Manager *m, Class *root)
												: EtPeTool(m, "Class Hierarchy")
{
	ct= new PeClassTreeView(this, root);
}

VObject *PeHierarchyBrowser::DoMakeContent()
{
	hierZoomer= new Zoomer(ct);
	hierZoomer->SetBgInk(gInkNone);
	SetFirstHandler(ct);
	return new VExpander(gPoint5,
		hierZoomer,
		0
	);
}

Point PeHierarchyBrowser::GetInitialWindowSize()
{
	return Point(600,600);
}

PeHierarchyBrowser::~PeHierarchyBrowser()
{
	SafeDelete(ct);
}

MenuBar *PeHierarchyBrowser::DoMakeMenuBar()
{
	MenuBar *mb= EtPeTool::DoMakeMenuBar();

	Menu *m= Zoomer::MakeMenu(cVIEWMENU);
	m->InsertItemsBefore(cZOOMOUT,
					"Tree",                    cTREEVIEW,
					"Outline",                 cOUTLINEVIEW,
					"-",
					"All Classes",             cALLCLASSES,
					"Abstract Classes",        cONLYABSTRACT,
					"Application Classes",     cONLYAPPL,
					"-",
					0);
	mb->AddMenu(m);

	m= new Menu("Classes");
	m->AppendItems(
					"Members",                  cMEMBERS,
					"Clients",                  cCLIENTS,
					"Show Source",              cSOURCE,
					"-",
					"Clear Lines",              cCLEARREFS,
					"-",
					"Collapse",                 cDOCOLLAPSE,
					"Promote",                  cPROMOTE,
					0);
	mb->AddMenu(m);
										
	return mb;
}

void PeHierarchyBrowser::DoSetupMenu(Menu *mp)
{
	EtPeTool::DoSetupMenu(mp);
	Clipper *vf= hierZoomer->GetClipper();
	if (vf->GetZoom() < 1024)
		mp->EnableItem(cZOOMIN);
	if (vf->GetZoom() > 0.01)
		mp->EnableItem(cZOOMOUT);
		
	mp->EnableItems(cZOOMRESET, cTREEVIEW, cOUTLINEVIEW, 0);
	mp->CheckItem(cOUTLINEVIEW, ct->GetLayout() == eTLIndented);
	mp->CheckItem(cTREEVIEW, ct->GetLayout() == eTLLeftRight);

	mp->CheckItem(cALLCLASSES, ct->Shows() == ePeCTShowAll);
	mp->CheckItem(cONLYABSTRACT, ct->Shows() == ePeCTShowAbstract);
	mp->CheckItem(cONLYAPPL, ct->Shows() == ePeCTShowApp);

	mp->EnableItems(cMEMBERS, cCLIENTS, cSOURCE, cDOCOLLAPSE, cCLEARREFS,
					cALLCLASSES, cONLYABSTRACT, cONLYAPPL, cPROMOTE, 0);
}

Command *PeHierarchyBrowser::DoMenuCommand(int cmd)
{
	Clipper *vf= hierZoomer->GetClipper();
	switch (cmd) {
	case cZOOMOUT:
		vf->SetZoom(vf->GetZoom()/1.2);
		break;
		
	case cZOOMIN:
		vf->SetZoom(vf->GetZoom()*1.2);
		break;
		
	case cZOOMRESET:
		vf->SetZoom(1.0);
		break;
		
	case cONLYABSTRACT:
		ct->ShowOnlyAbstractClasses();
		break;
		
	case cONLYAPPL:
		ct->ShowOnlyApplicationClasses();
		break;
		
	case cALLCLASSES:
		ct->ShowAllClasses();
		break;
		
	case cCLEARREFS:
		ct->RemoveAllReferences();
		break;
		
	default:
		return EtPeTool::DoMenuCommand(cmd);
	}
	return gNoChanges;
}

void PeHierarchyBrowser::ShowClass(Class *cp)
{
	ct->ShowClass(cp);
}

void PeHierarchyBrowser::Spawn(Manager *m, Class *root)
{
	PeHierarchyBrowser *hb= new PeHierarchyBrowser(m, root);
	hb->SetOnDismiss(eMgrClose);
	hb->Show();
}

void PeHierarchyBrowser::ShowClass(Manager *m, Class *selection)
{
	if (hierBrowser == 0)
		hierBrowser= new PeHierarchyBrowser(m, gClassManager->Find("Object"));
	hierBrowser->Show();
	hierBrowser->ShowClass(selection);
}

